#include "object.h"
#include "eventServer.h"
#include "fileServer.h"
#include "networkServer.h"

#include "boost/date_time/posix_time/posix_time.hpp"

#include <cstring>
#include <sstream>
#include <cstdio>
#include <ctime>
#include <stdarg.h>
#include <signal.h>

namespace sde {

    boost::python::object Object::mainNamespace;
    boost::python::object Object::PythonObject;
    boost::python::object Object::hasAttr;
    boost::python::object Object::glob;
    boost::python::object Object::osPath;
    boost::python::object Object::PythonVector2;
    boost::python::object Object::PythonVector3;
    boost::python::object Object::PythonQuaternion;
    
    StringObjectMap Object::allObjects;
    bool Object::shutdown = false;
    
    Object::Object(std::string desiredName, bool fromServer) {
        if (fromServer) 
            name = desiredName;
        else
            name = NetworkServer::getSingleton().makeObjectName(desiredName);
        allObjects[name] = this;
//         log("created");
    }
    
    Object::~Object() {
        stopAllActions();
        allObjects.erase(name);
//         log("destroyed");
    }
    
    std::string Object::makeName(std::string desiredName) {
        std::ostringstream sout;
        std::string name;
        int i = 1;
        do {
            sout << desiredName;
            if (i > 1)
                sout << "_" << i;
            i++;
            name = sout.str();
            sout.str("");
        } while (hasObject(name));
        return name;
    }
    
    void Object::log(const char* fmt, ...) {
        va_list args;
        va_start(args, fmt);
        char buffer[1024];
        vsnprintf(buffer,1024,fmt,args);
        printf("%s: %s\n", name.c_str(), buffer);
        va_end(args);
    }
    
    Object* Object::getObject(std::string objectName) {
        if (hasObject(objectName))
            return allObjects[objectName];
        else
            return 0;
    }
    
    bool Object::hasObject(std::string objectName) {
        return allObjects.find(objectName) != allObjects.end();
    }
    
    bool Object::deleteObject(std::string objectName, bool tellServer) {
        if (hasObject(objectName)) {
            if (tellServer)
                NetworkServer::getSingleton().destroyObject(objectName);
            delete getObject(objectName);
            return true;
        } else
            return false;
    }
    
    void Object::deleteAllObjects(bool tellServer) {
        for (StringObjectMap::iterator i = allObjects.begin(); i != allObjects.end(); ++i) {
            deleteObject(i->first,tellServer);
            //delete i->second;
        }
    }
    
    bool Object::hasVar(std::string varName) {
        requestVar(varName);
        return vars.find(varName) != vars.end();
    }
    
    Var Object::getVar(std::string varName, Var def) {
        requestVar(varName);
        if (hasVar(varName))
            return vars[varName];
        else
            return def;
    }
    
/*    Var Object::defaultVar(std::string varName, Var val) {
        if (!hasVar(varName)) {
            log("set default var %s",varName.c_str());
            setVar(varName,val);
        }
        return getVar(varName,val);
    }
    */
    void Object::setVar(std::string varName, Var val, bool tellServer) {
        if (tellServer)
            NetworkServer::getSingleton().setVar(name,varName,val);
    
        vars[varName] = val;
        
        if (setVarFunctions.find(varName) != setVarFunctions.end())
            for (SetVarFunctionList::iterator i = setVarFunctions[varName].begin(); i != setVarFunctions[varName].end(); ++i)
                (*i)(val);
                
        if (pythonSetVarFunctions.find(varName) != pythonSetVarFunctions.end()) {
            boost::python::object pyVal = VarToPythonObject(val);
            for (PythonObjectList::iterator i = pythonSetVarFunctions[varName].begin();
                i != pythonSetVarFunctions[varName].end(); ++i)
                (*i)(varName,pyVal);
        }
    }
    
    void Object::distSetVar(std::string varName, Var val) {
        setVar(varName,val,false);
        
        NetworkServer::getSingleton().distSetVar(name,varName,val);
    }
    
    void Object::subscribeRpc(std::string cmd, RpcFunction function) {
        if (hasRpc(cmd))
            log("WARNING: rpc already registerd for %s on object %s",cmd.c_str(),name.c_str());
        rpcFunctions[cmd] = function;
        NetworkServer::getSingleton().subscribeRpc(name,cmd);
    }
    
    Var Object::doCallRpc(std::string cmd, VarMap args) {
        if (hasCRpc(cmd))
            return rpcFunctions[cmd](args);
        else if (hasPythonRpc(cmd)) {
            boost::python::object pyArgs = VarMapToPythonDict(args);
            boost::python::object pyResult = pythonRpcFunctions[cmd](cmd,pyArgs);
            return PythonObjectToVar(pyResult);
        }
        else
            return Var();
    }
    
    RpcDeferral Object::deferRpc(std::string cmd, VarMap args) {
        NetworkServer::getSingleton().requestRpc(name,cmd,args);
        RpcDeferral defer;
        defer.objectName = name;
        defer.cmd = cmd;
        return defer;
    }
    
    Var Object::finishRpc(std::string cmd) {
        return NetworkServer::getSingleton().finishRpc(name,cmd);
    }
    
    Var Object::callRpc(std::string cmd, VarMap args) {
        return deferRpc(cmd,args)();
    }
    
    bool Object::hasRpc(std::string cmd) {
        return hasCRpc(cmd) || hasPythonRpc(cmd);
    }
    
    bool Object::hasCRpc(std::string cmd) {
        return rpcFunctions.find(cmd) != rpcFunctions.end();
    }
    
    bool Object::hasPythonRpc(std::string cmd) {
        return pythonRpcFunctions.find(cmd) != pythonRpcFunctions.end();
    }
    
    bool Object::rpcIsLocal(std::string objectName, std::string cmd) {
        Object* object = Object::getObject(objectName);
        return object && object->hasRpc(cmd);
    }
    
    void Object::subscribeSetVar(std::string varName, SetVarFunction function) {
        requestVar(varName);
        if (setVarFunctions.find(varName) == setVarFunctions.end())
            setVarFunctions[varName] = SetVarFunctionList();
        setVarFunctions[varName].push_back(function);
    }
    
    void Object::requestVar(std::string varName) {
        bool haveRequestedVar = false;
        for (StringList::iterator i = requestedVars.begin(); !haveRequestedVar && i != requestedVars.end(); ++i)
            if (*i == varName)
                haveRequestedVar = true;
        if (!haveRequestedVar) {
            NetworkServer::getSingleton().requestVar(name,varName);
            requestedVars.push_back(varName);
        }
    }
    
    void Object::updateVars(VarMap vals, bool tellServer) {
        for (VarMap::iterator i = vals.begin(); i != vals.end(); ++i)
            setVar(i->first,i->second,tellServer);
    }
    
    bool Object::hasAction(std::string actionName) {
        return actions.find(actionName) != actions.end();
    }
    
    void Object::doStopAction(Action* action) {
        for (StringList::iterator i = action->destroyDepends.begin(); i != action->destroyDepends.end(); ++i) {
            ActionList deps = Action::getAllActionsOfArchetype(*i);
            for (ActionList::iterator j = deps.begin(); j != deps.end(); ++j) {
                (*j)->doStop();
            }
        }
    
        action->doStop();
    }
    
    void Object::stopAllActions() {
        for (StringActionMap::iterator i = actions.begin(); i != actions.end(); ++i) {
            doStopAction(i->second);
            delete i->second;
        }
        actions.clear();
    }
    
    bool Object::stopAction(std::string actionName) {
        Action* action = getAction(actionName);
        if (action) {
            doStopAction(action);
            actions.erase(action->name);
            delete action;
            return true;
        } else
            return false;
    }
    
    Action* Object::getAction(std::string actionName) {
        if (hasAction(actionName))
            return actions[actionName];
        else
            return 0;
    }
    
    Action* Object::getFirstActionOfArchetype(std::string type) {
        for (StringActionMap::iterator i = actions.begin(); i != actions.end(); ++i)
            if (i->second->archetype == type)
                return i->second;
        return 0;
    }
    
    Action* Object::searchForAction(std::string actionName) {
        for (StringObjectMap::iterator i = allObjects.begin(); i != allObjects.end(); ++i)
            if (i->second->hasAction(actionName))
                return i->second->getAction(actionName);
        return 0;
    }
    
    std::string Object::runAction(std::string actionType, std::string actionName) {
        return NetworkServer::getSingleton().runAction(name,actionType,actionName);
    }
    
    void Object::doRunAction(std::string actionType, std::string actionName) {
        Action* action = actionFactory(actionType, actionName);
        if (action) {
            actions[action->name] = action;
            
            if (action->initDepend == "") {
                action->doRun();
                for (ActionList::iterator i = Action::allActions.begin(); i != Action::allActions.end(); ++i)
                    if ((*i)->initDepend == action->archetype) {
                        (*i)->doRun();
                        action->destroyDepends.remove((*i)->archetype);
                        action->destroyDepends.push_back((*i)->archetype);
                    }
            } else {
                for (ActionList::iterator i = Action::allActions.begin(); i != Action::allActions.end(); ++i)
                    if ((*i)->archetype == action->initDepend && (*i)->started)
                        action->doRun();
            }
            
        } else
            log("ERROR: unable to run action %s",actionType.c_str());
    }
    
    void shutdownSignal(int sig) {
        printf("ctrl-c\n");
        Object::shutdown = true;
    }
    
    void Object::runSimulation(int argc, char** argv) {
        signal(SIGABRT,&shutdownSignal);
        signal(SIGTERM,&shutdownSignal);
        signal(SIGINT,&shutdownSignal);
    
        initPython();
    
        FileServer::getSingleton().loadModules(argc,argv);
        NetworkServer::getSingleton().connectClient();
        FileServer::getSingleton().loadSimFiles();
    
        float startTime = getTime(), lastTime = startTime, dtime, currentTime;
        int numFrames = 0;
        VarMap args;
        
        while (!shutdown) {
            numFrames++;
            while (currentTime == lastTime)
                currentTime = getTime() - startTime;
            dtime = currentTime - lastTime;
            lastTime = currentTime;
            args["time"] = currentTime;
            args["dtime"] = dtime;
            EventServer::getSingleton().publish("update",args);
        }
        
        float totalTime = getTime() - startTime,
            fps = float(numFrames) / totalTime;
        
        printf("%d frames in %f secs = %f fps\n",numFrames,totalTime,fps);
    
        NetworkServer::getSingleton().disconnect();
    }
    
    void Object::shutdownSimulation() {
        shutdown = true;
    }
    
    float Object::getTime() {
        return float(boost::posix_time::microsec_clock::universal_time()
                .time_of_day().total_nanoseconds()) / 1000000000.0;
        //return float(std::clock()) / float(CLOCKS_PER_SEC);
    }
    
    void Object::loadFile(std::string filename) {
        TiXmlDocument doc(filename.c_str());
        if (doc.LoadFile()) {
//             log("load %s",filename.c_str());
            TiXmlElement* objectElement = doc.FirstChildElement("object");
            if (objectElement)
                load(objectElement);
        } else
            log("ERROR: failed to open %s",filename.c_str());
    }
    
    void Object::load(TiXmlElement* element) {
        TiXmlElement* childElement = element->FirstChildElement();
        while (childElement) {
            const char* tag = childElement->Value();
            if (tag) {
                if (!strcmp(tag,"var"))
                    loadVar(childElement);
                else if (!strcmp(tag,"action"))
                    loadAction(childElement);
                else if (!strcmp(tag,"script"))
                    loadScript(childElement);
                else if (!strcmp(tag,"include"))
                    loadInclude(childElement);
                else
                    log("ERROR: unknown tag %s",tag);
            }
            childElement = childElement->NextSiblingElement();
        }
    }
    
    void Object::loadVar(TiXmlElement* element) {
        const char *name = element->Attribute("name"),
                   *type = element->Attribute("type"),
                   *value = element->Attribute("value");
        if (name && type && value) {
            if (!strcmp(type,"bool")) {
                int val;
                sscanf(value,"%d",&val);
                distSetVar(name,Var(val != 0));
            } else if (!strcmp(type,"int")) {
                int val;
                sscanf(value,"%d",&val);
                distSetVar(name,Var(val));
            } else if (!strcmp(type,"float")) {
                float val;
                sscanf(value,"%f",&val);
                distSetVar(name,Var(val));
            } else if (!strcmp(type,"string")) {
                distSetVar(name,Var(std::string(value)));
            } else if (!strcmp(type,"vector2")) {
                float x,y;
                sscanf(value,"%f%f",&x,&y);
                distSetVar(name,Var(Vector2(x,y)));
            } else if (!strcmp(type,"vector3")) {
                float x,y,z;
                sscanf(value,"%f%f%f",&x,&y,&z);
                distSetVar(name,Var(Vector3(x,y,z)));
            } else if (!strcmp(type,"quaternion")) {
                float a,x,y,z;
                sscanf(value,"%f%f%f%f",&a,&x,&y,&z);
                distSetVar(name,Var(Quaternion(Degree(a),Vector3(x,y,z))));
            } else {
                log("ERROR: unknown var type %s",type);
            }
        } else {
            log("ERROR: var tag missing some attributes");
        }
    }
    
    void Object::loadAction(TiXmlElement* element) {
        const char *type = element->Attribute("type"),
                   *name = element->Attribute("name");
        if (type) {
            std::string sname;
            if (name)
                sname = name;
            else
                sname = type;
            runAction(type,sname);
        } else
            log("ERROR: action tag missing some attributes");
    }
    
    void Object::loadScript(TiXmlElement* element) {
        const char *name = element->Attribute("name");
        if (name)
            runScript(name);
        else
            log("ERROR: script tag missing some attributes");
    }
    
    void Object::loadInclude(TiXmlElement* element) {
        const char *name = element->Attribute("name");
        if (name)
            loadFile(name);
        else
            log("ERROR: include tag missing some attributes");
    }
    
    void Object::runScript(std::string filename) {
        std::string fullFilename = FileServer::getSingleton().getFileByName(filename);
        if (fullFilename.size()) {
            boost::python::dict scriptNamespace = boost::python::dict(mainNamespace);
            
            scriptNamespace["self"] = PythonObject(name);
            scriptNamespace["scriptFilename"] = fullFilename;
            
            try {
                boost::python::object ignored = boost::python::exec_file(fullFilename.c_str(),scriptNamespace,scriptNamespace);
            
                boost::python::object items = scriptNamespace.attr("items")();
                int numItems = boost::python::extract<int>(items.attr("__len__")());
                for (int i = 0; i < numItems; ++i) {
                    std::string funcName = boost::python::extract<std::string>(items[i][0]);
                    boost::python::object func = items[i][1];
                    if (hasAttr(func,"__class__")) {
                        std::string funcType = boost::python::extract<std::string>(
                            func.attr("__class__").attr("__name__"));
                        if (funcType == "function") {
                            int pos = funcName.find("_");
                            if (pos != std::string::npos) {
                                std::string prefix = funcName.substr(0,pos);
                                std::string op = funcName.substr(pos+1);
                                
                                if (prefix == "receive") {
                                    if (pythonSubscriptions.find(op) == pythonSubscriptions.end()) {
                                        pythonSubscriptions[op] = PythonObjectList();
                                        EventServer::getSingleton().subscribe(
                                            op,boost::bind(&Object::receiveForPython,this,_1));
                                    }
                                    pythonSubscriptions[op].push_back(func);
                                    
                                } else if (prefix == "setVar") {
                                    if (pythonSetVarFunctions.find(op) == pythonSetVarFunctions.end())
                                        pythonSetVarFunctions[op] = PythonObjectList();
                                    pythonSetVarFunctions[op].push_back(func);
                                    
                                } else if (prefix == "rpc") {
                                    if (hasRpc(op))
                                        log("WARNING: overwriting registered rpc with python function %s in file %s",
                                            funcName.c_str(),fullFilename.c_str());
                                    if (!hasPythonRpc(op))
                                        NetworkServer::getSingleton().subscribeRpc(name,op);
                                    pythonRpcFunctions[op] = func;
                                
                                } else {
                                    log("ERROR: invalid function type %s for function %s in script %s",
                                        prefix.c_str(),funcName.c_str(),fullFilename.c_str());
                                } 
                            }
                        }
                    }
                }
            } catch (boost::python::error_already_set const&) {
                log("ERROR: running script %s:",fullFilename.c_str());
                PyErr_Print();
            }
            
            
        } else
            log("ERROR: couldn't run script %s",filename.c_str());
    }
    
    void Object::receiveForPython(VarMap args) {
        std::string cmd = boost::any_cast<std::string>(args["cmd"]);
        if (pythonSubscriptions.find(cmd) != pythonSubscriptions.end()) {
            boost::python::dict dict = VarMapToPythonDict(args);
            for (PythonObjectList::iterator i = pythonSubscriptions[cmd].begin(); i != pythonSubscriptions[cmd].end(); ++i) {
                try {
                    (*i)(cmd,dict);
                } catch (boost::python::error_already_set const&) {
                    log("ERROR: running python subscriber");
                    PyErr_Print();
                }
            }
        }
    }
    
    void Object::initPython() {
        Py_Initialize();
    
        boost::python::object mainModule = boost::python::import("__main__");
        mainNamespace = boost::python::dict(mainModule.attr("__dict__"));
        
        mainNamespace["hasObject"] = boost::python::make_function(Object::hasObject);
        mainNamespace["hasVar"] = boost::python::make_function(Object::pyHasVar);
        mainNamespace["setVar"] = boost::python::make_function(Object::pySetVar);
        mainNamespace["getVar"] = boost::python::make_function(Object::pyGetVar);
        mainNamespace["runAction"] = boost::python::make_function(Object::pyRunAction);
        mainNamespace["stopAction"] = boost::python::make_function(Object::pyStopAction);
        mainNamespace["stopAllActions"] = boost::python::make_function(Object::pyStopAllActions);
        mainNamespace["load"] = boost::python::make_function(Object::pyLoad);
        mainNamespace["shutdown"] = boost::python::make_function(Object::shutdownSimulation);
        mainNamespace["deleteObject"] = boost::python::make_function(Object::pyDeleteObject);
        mainNamespace["makeObject"] = boost::python::make_function(Object::pyMakeObject);
        mainNamespace["log"] = boost::python::make_function(Object::pyLog);
        mainNamespace["runScript"] = boost::python::make_function(Object::pyRunScript);
        mainNamespace["publish"] = boost::python::make_function(Object::pyPublish);
        mainNamespace["getObjectNames"] = boost::python::make_function(Object::pyGetObjectNames);
        mainNamespace["requestRpc"] = boost::python::make_function(Object::pyRequestRpc);
        mainNamespace["finishRpc"] = boost::python::make_function(Object::pyFinishRpc);
        
        boost::python::object ignored = boost::python::exec_file("system/pythonObject.py",mainNamespace,mainNamespace);
        PythonObject = mainNamespace["PythonObject"];
        hasAttr = mainNamespace["__builtins__"].attr("hasattr");
        glob = mainNamespace["glob"].attr("glob");
        osPath = mainNamespace["path"];
        
        PythonVector3 = boost::python::class_<Vector3>("Vector3", boost::python::init<>())
            .def(boost::python::init<float, float, float>())
            .def_readwrite("x",&Vector3::x)
            .def_readwrite("y",&Vector3::y)
            .def_readwrite("z",&Vector3::z)
            .def("__repr__",&Vector3::toString)
        ;
        
        mainNamespace["Vector3"] = PythonVector3;
        
        PythonVector2 = boost::python::class_<Vector2>("Vector2", boost::python::init<>())
            .def(boost::python::init<float, float>())
            .def_readwrite("x",&Vector2::x)
            .def_readwrite("y",&Vector2::y)
            .def("__repr__",&Vector2::toString)
        ;
        
        mainNamespace["Vector2"] = PythonVector2;
        
        PythonQuaternion = boost::python::class_<Quaternion>("Quaternion", boost::python::init<>())
            .def(boost::python::init<float, boost::python::object>())
            .def(boost::python::init<float,float,float,float>())
            .def_readwrite("x",&Quaternion::x)
            .def_readwrite("y",&Quaternion::y)
            .def_readwrite("z",&Quaternion::z)
            .def_readwrite("w",&Quaternion::w)
            .def("__repr__",&Quaternion::toString)
        ;
        
        mainNamespace["Quaternion"] = PythonQuaternion;
    }
    
    bool Object::pyHasVar(std::string objectName, std::string varName) {
        Object* object = getObject(objectName);
        return object ? object->hasVar(varName) : false;
    }
    
    void Object::pySetVar(std::string objectName, std::string varName, boost::python::object val) {
        Object* object = getObject(objectName);
        if (object)
            object->setVar(varName,PythonObjectToVar(val));
    }
    
    boost::python::object Object::pyGetVar(std::string objectName, std::string varName) {
        Object* object = getObject(objectName);
        if (object && object->hasVar(varName))
            return VarToPythonObject(object->getVar(varName));
        else
            return boost::python::object();
    }
    
    boost::python::object Object::pyRunAction(std::string objectName, std::string actionType, std::string actionName) {
        Object* object = getObject(objectName);
        if (!object)
            return boost::python::object();
        std::string actionRealName = object->runAction(actionType,actionName);
        return boost::python::object(actionRealName);
    }
    
    bool Object::pyStopAction(std::string objectName, std::string actionName) {
        Object* object = getObject(objectName);
        return object && object->stopAction(actionName);
    }
    
    void Object::pyStopAllActions(std::string objectName) {
        Object* object = getObject(objectName);
        if (object)
            object->stopAllActions();
    }
    
    void Object::pyLoad(std::string objectName, std::string filename) {
        Object* object = getObject(objectName);
        if (object)
            object->loadFile(filename);
    }
    
    std::string Object::pyMakeObject(std::string objectName) {
        Object* object = new Object(objectName);
        return object->name;
    }
    
    bool Object::pyDeleteObject(std::string objectName) {
        return Object::deleteObject(objectName);
    }
    
    void Object::pyLog(std::string objectName, std::string msg) {
        Object* object = getObject(objectName);
        if (object)
            object->log(msg.c_str());
    }
    
    void Object::pyRunScript(std::string objectName, std::string scriptName) {
        Object* object = getObject(objectName);
        if (object)
            object->runScript(scriptName);
    }
    
    void Object::pyPublish(std::string cmd, boost::python::object args) {
        EventServer::getSingleton().publish(cmd,PythonDictToVarMap(args));
    }
    
    boost::python::object Object::pyGetObjectNames() {
        boost::python::list names;
        for (StringObjectMap::iterator i = allObjects.begin(); i != allObjects.end(); ++i)
            names.append(i->first);
        return names;
    }
    
    void Object::pyRequestRpc(std::string objectName, std::string cmd, boost::python::object pyArgs) {
        VarMap args = PythonDictToVarMap(pyArgs);
        NetworkServer::getSingleton().requestRpc(objectName,cmd,args);
    }
    
    boost::python::object Object::pyFinishRpc(std::string objectName, std::string cmd) {
        Var result = NetworkServer::getSingleton().finishRpc(objectName,cmd);
        return VarToPythonObject(result);
    }
    
    Var RpcDeferral::operator()() {
        Object* object = Object::getObject(objectName);
        if (object)
            return object->finishRpc(cmd);
        else
            return Var();
    }
}
